package koordinator;

import ggt.unit;
import ggt.unitHelper;

import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.swing.JOptionPane;

import org.omg.CORBA.IntHolder;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNaming.NamingContextPackage.CannotProceed;
import org.omg.CosNaming.NamingContextPackage.InvalidName;
import org.omg.CosNaming.NamingContextPackage.NotFound;

import chef._koordinatorImplBase;

/**
 * @author pascal
 *
 */
public class Koordinator extends _koordinatorImplBase {

	private Logger logger;
	private NamingContext ctx;
	// values from gui
	private int minDelay = 1;
	private int maxDelay = 2;
	private int minGGTProcesses = 3;
	private int maxGGTProcesses = 3;
	private int timeout = 5;
	private int ggt = 31415;

	Map<Integer, unit> pidUnits = new Hashtable<Integer, unit>();
	Map<Integer, unit> unitsToClose = new Hashtable<Integer, unit>();
	NumberFormat pidFormat = new DecimalFormat("00000");
	NumberFormat timeFormat = new DecimalFormat("000000");
	private KoordinatorGUI koordinatorGUI;

	public Koordinator(NamingContext ctx) {
		this.ctx = ctx;
		logger = getLogger();
	}

	/**
	 * ggT Process sends koordinator its name (pid), current number Mi (pm) and
	 * current system time (ptime) represented by (hhmmss).
	 * 
	 * @param pid
	 *            process name/(id
	 * @param pm
	 *            calculated ggT
	 * @param ptime
	 *            current system time
	 * 
	 *            (non-Javadoc)
	 * @see chef.koordinatorOperations#brief(int, int, int)
	 */
	@Override
	public void brief(int pid, int pm, int ptime) {
		logAndWrite(Level.INFO, "Brief received from " + pidFormat.format(pid)
				+ " pm=" + pm + (pm != ggt ? " not " : " ") + "finished @"
				+ timeFormat.format(ptime));
	}

	/**
	 * sending pid, ggT and ptime as acknowledg of shutting down
	 * 
	 * @param pid
	 *            process name/id
	 * @param pm
	 *            the calculated ggT
	 * @param ptime
	 *            current system time
	 * 
	 *            (non-Javadoc)
	 * @see chef.koordinatorOperations#closed(int, int, int)
	 */
	@Override
	public void closed(int pid, int pm, int ptime) {
		logAndWrite(Level.INFO, "Closed received from " + pidFormat.format(pid)
				+ " pm=" + pm + (pm != ggt ? " not " : " ") + "finished @"
				+ timeFormat.format(ptime));
		// KoordinatorGUI.model.drop(pidFormat.format(pid), true);
	}

	/**
	 * Returns information to caller (starter) which are
	 * 
	 * @param pnum
	 *            number of ggT- processes
	 * @param wtime
	 *            delay for processes in seconds
	 * @param term
	 *            timeout
	 * 
	 * @return 0 = error, 1 = ok
	 * 
	 *         (non-Javadoc)
	 * @see chef.koordinatorOperations#getsteeringval(org.omg.CORBA.IntHolder,
	 *      org.omg.CORBA.IntHolder, org.omg.CORBA.IntHolder)
	 */
	@Override
	public int getsteeringval(IntHolder pnum, IntHolder wtime, IntHolder term) {
		// if (koordState.getState() != states.initial) {
		// logger.log(Level.INFO, "Getsteeringval refused - wrong zustand");
		// return 0;
		// }

		pnum.value = getRandom(minGGTProcesses, maxGGTProcesses);
		wtime.value = getRandom(minDelay, maxDelay);
		term.value = timeout;

		logAndWrite(Level.INFO, "Getsteeringval accepted (num:" + pnum.value
				+ ", delay:" + wtime.value + ", timeout:" + timeout + ")");
		return 1;
	}

	/**
	 * sending process's name to koordinator (no _slave postfix)
	 * 
	 * @param pid
	 *            process name/id
	 * 
	 * @return 0 = error, 1 = ok
	 * 
	 *         (non-Javadoc)
	 * @see chef.koordinatorOperations#hello(int)
	 */
	@Override
	public int hello(int pid) {
		// if (zustand != Zustand.INITIAL) {
		// msg.setMessage("hello from " + pidFormat.format(pid) +
		// " refused - wrong zustand");
		// return 0;
		// }
		if (!pidUnits.containsKey(pid)) {
			// pm.addProzess(pidFormat.format(pid));
		}

		pidUnits.put(pid, resolve(pid));
		koordinatorGUI.addPidToList(pid);
		logAndWrite(Level.INFO, "Hello from " + pidFormat.format(pid));
		return 1;
	}

	/**
	 * Is called if process recognized termination sends koordinator
	 * 
	 * @param pid
	 *            process name/id
	 * @param ggt
	 *            the calculated ggT
	 * @param ptime
	 *            current system time
	 * 
	 *            (non-Javadoc)
	 * @see chef.koordinatorOperations#terminated(int, int, int)
	 */
	@Override
	public void terminated(int pid, int ggt, int ptime) {
		logAndWrite(Level.INFO, "Terminated received from "
				+ pidFormat.format(pid) + " pm=" + ggt
				+ (ggt != this.ggt ? " not " : " ") + "finished @"
				+ timeFormat.format(ptime));
		 koordinatorGUI.removePidFromList(pid);
		 
		 pidUnits.remove(pid);
		 if(pidUnits.isEmpty()){
			 sendCloseSignal();
		 }
	}

	private void sendCloseSignal() {
		Set<Entry<Integer, unit>> set = unitsToClose.entrySet();
		for(Entry<Integer, unit> u : set) {
			u.getValue().close();
		}
	}

	/**
	 * Returns a randomly chosen number from the given interval
	 * 
	 * @param min
	 *            the lower end of the interval (inclusive)
	 * @param max
	 *            the upper end of the interval (inclusive)
	 * 
	 * @return a randomly chosen number
	 */
	private int getRandom(int min, int max) {
		if (min == max) {
			return min;
		}
		if (min > max) {
			return getRandom(max, min);
		}
		return min + new Random().nextInt(max - min);
	}

	/**
	 * Creates a logger.
	 * 
	 * @return
	 */
	public Logger getLogger() {
		FileHandler fhandler = null;
		if (logger == null) {
			try {
				fhandler = new FileHandler("Koordinator.log");
				fhandler.setFormatter(new SimpleFormatter());
				logger = Logger.getLogger("ServerLog");
				logger.addHandler(fhandler);
				logger.setLevel(Level.INFO);
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return logger;
	}

	/**
	 * A simple dialog.
	 * 
	 * @param titel
	 * @param text
	 */
	public void showMessage(String titel, String text) {
		JOptionPane.showMessageDialog(null, text, titel,
				JOptionPane.INFORMATION_MESSAGE);
	}

	/**
	 * A simple error dialog.
	 * 
	 * @param titel
	 * @param text
	 */
	public void showMessageError(String titel, String text) {
		JOptionPane.showMessageDialog(null, text, titel,
				JOptionPane.ERROR_MESSAGE);
	}

	/**
	 * Tell koordinator the values from the gui
	 * 
	 * @param minDelay
	 *            min delay for processes
	 * @param maxDelay
	 *            max delay for processes
	 * @param minProcesses
	 *            min amout of processes for one starter
	 * @param maxProcesses
	 *            max amout of processes for one starter
	 * @param timeout
	 *            processes timeout
	 * @param ggt
	 *            the ggt value we have to calculate
	 */
	public void receiveGuiValues(int minDelay, int maxDelay, int minProcesses,
			int maxProcesses, int timeout, int ggt) {
		this.minDelay = minDelay;
		this.maxDelay = maxDelay;
		this.minGGTProcesses = minProcesses;
		this.maxGGTProcesses = maxProcesses;
		this.timeout = timeout;
		this.ggt = ggt;
	}

	public void setGUI(KoordinatorGUI koordinatorGUI) {
		this.koordinatorGUI = koordinatorGUI;
	}

	private unit resolve(int pid) {
		NameComponent name[] = new NameComponent[] { new NameComponent("slave"
				+ pidFormat.format(pid), "") };

		try {
			return (unit) unitHelper.narrow(ctx.resolve(name));
		} catch (NotFound e) {
			e.printStackTrace();
		} catch (CannotProceed e) {
			e.printStackTrace();
		} catch (InvalidName e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 
	 */
	protected void buildRing() {
		logAndWrite(Level.INFO, "Build Ring");
		Integer[] pidArray = new Integer[pidUnits.size()];
		pidUnits.keySet().toArray(pidArray);
		Collections.shuffle(Arrays.asList(pidArray));
		int retVal = 0;
		for (int i = 0; i < pidArray.length; i++) {
			if (i == 0) {
				retVal = pidUnits.get(pidArray[i]).setneighbors(
						pidArray[pidArray.length - 1], pidArray[i + 1]);
			} else if (i == pidArray.length - 1) {
				retVal = pidUnits.get(pidArray[i]).setneighbors(
						pidArray[i - 1], pidArray[0]);
			} else {
				retVal = pidUnits.get(pidArray[i]).setneighbors(
						pidArray[i - 1], pidArray[i + 1]);
			}

			logAndWrite(Level.INFO, i + " setneighbors "
					+ (retVal == 1 ? "ok" : "fehler"));
		}
	}

	/**
	 * Starts the calculation of the distributed ggt-algorithm
	 * b sending all registered process their mi value and then sending
	 * 3 process a start value
	 */
	protected void startCalculation() {
		logAndWrite(Level.INFO, "Start Calculation");
		
		// backup list for closing notification
		Set<Integer> intset = pidUnits.keySet();
		for(int i : intset){
			unitsToClose.put(i, pidUnits.get(i));
		}
		
		
		int idx, y;

		// send all processes there mi value
		for (unit u : pidUnits.values()) {
			y = ggt * getRandom(1, 100) * getRandom(1, 100);
			System.out.println("ggt: " + ggt);
			u.setpm(y);

			logAndWrite(Level.INFO, "setting pm=" + y);
		}

		// choose 3 processes randomly and send y value to start
		unit[] units = new unit[pidUnits.size()];
		pidUnits.values().toArray(units);
		for (int i = 0; i < 3; i++) {
			idx = getRandom(0, units.length - 1);
			y = ggt * getRandom(100, 1000);
			units[idx].sendy(y);

			logAndWrite(Level.INFO, "starting - sending " + y + " to pid #"
					+ idx);
		}
	}

	/**
	 * Transforms given pid into string with "slave" prefixed
	 * 
	 * @param pid the pid to transform into a string
	 * @return "slave'pid'"
	 */
	public String getSlaveString(int pid) {
		return "slave" + pidFormat.format(pid);
	}

	/**
	 * Writes text to logger and the gui
	 * 
	 * @param level the log-level
	 * @param text the text to log
	 */
	protected void logAndWrite(Level level, String text) {
		logger.log(level, text);
		koordinatorGUI.getMessageTextArea().setText(
		koordinatorGUI.getMessageTextArea().getText() + text + "\n");
		koordinatorGUI.getMessageTextArea().setCaretPosition(
		koordinatorGUI.getMessageTextArea().getDocument().getLength());
	}
}
